package asruby.lang
{
import asruby.Ruby;
import asruby.runtime.CallbackFactory;
import asruby.runtime.ClassIndex;
import asruby.runtime.IRubyObject;
import asruby.runtime.ObjectAllocator;
import asruby.runtime.ThreadContext;
	
public class RubyFixnum extends RubyNumeric
{
   public static function createFixnumClass(runtime: Ruby): RubyClass {
        var fixnum: RubyClass = runtime.defineClass("Fixnum", runtime.getInteger(),
                ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
        runtime.setFixnum(fixnum);
        fixnum.index = ClassIndex.FIXNUM;
        fixnum.kindOf = function  isKindOf(obj: IRubyObject, type: RubyModule): Boolean {
                    return obj instanceof RubyFixnum;
                };
        var callbackFactory: CallbackFactory = runtime.callbackFactory(RubyFixnum);

        //fixnum.includeModule(runtime.getPrecision());
        
        fixnum.defineAnnotatedMethods(RubyFixnum);
        
        fixnum.dispatcher = callbackFactory.createDispatcher(fixnum);
        
        for (var i: int = 0; i < runtime.fixnumCache.length; i++) {
            runtime.fixnumCache[i] = new RubyFixnum(runtime, fixnum, i - 128);
        }

        return fixnum;
    }    
    
    private  var value: Number;
    private static  var BIT_SIZE: int = 64;
    public static var SIGN_BIT:Number = (1 << (BIT_SIZE - 1));
    public static var MAX:Number = (1 <<(BIT_SIZE - 1)) - 1;
    public static  var MIN:Number = -1 * MAX -1;
    public static var MAX_MARSHAL_FIXNUM:Number = (1 << 30) - 1; // 0x3fff_ffff
    public static var MIN_MARSHAL_FIXNUM:Number = - (1 << 30);   // -0x4000_0000
    
    public function RubyFixnum(runtime: Ruby, klazz: RubyClass, value: Number) {
        super(runtime, klazz, false);
        this.value = value;
    }
    
    public override function  getNativeTypeIndex(): int {
        return ClassIndex.FIXNUM;
    }

 /** 
     * short circuit for Fixnum key comparison
     */
    public override function eql(other:IRubyObject):Boolean{
        return other instanceof RubyFixnum && value == RubyFixnum(other).value;
    }
    
    public override function isImmediate():Boolean{
    	return true;
    }
    
    public override function getSingletonClass():RubyClass{
        throw getRuntime().newTypeError("can't define singleton");
    }

    public function getAsClass():Class {
        return Number;
    }

    public override function getDoubleValue():Number{
        return value;
    }

    public override function getLongValue():Number{
        return value;
    }

    public static function newFixnum(runtime:Ruby, value:Number):RubyFixnum{
        var offset: int =  128;
        if (value <= 127 && value >= -128) {
            return runtime.fixnumCache[ value + offset];
        }
        return new RubyFixnum(runtime, value);
    }

    public  function newFixnum(newValue:Number):RubyFixnum{
        return newFixnum(getRuntime(), newValue);
    }

    public static function zero(runtime:Ruby):RubyFixnum{
        return newFixnum(runtime, 0);
    }

    public static function one(runtime:Ruby):RubyFixnum{
        return newFixnum(runtime, 1);
    }

    public static function minus_one(runtime:Ruby):RubyFixnum{
        return newFixnum(runtime, -1);
    }

    public override function hash():RubyFixnum{
        return newFixnum(hashCode());
    }

    public override function hashCode():int{
        return (int)(value ^ value >>> 32);
    }

    public override function equals(other:Object):Boolean{
        if (other == this) {
            return true;
        }
        
        if (other instanceof RubyFixnum) { 
            var num: RubyFixnum =  RubyFixnum(other);
            
            if (num.value == value) {
                return true;
            }
        }
        
        return false;
    }

    /*  ================
     *  Instance Methods
     *  ================ 
   

    // fix_to_s
    public override function to_s(args:Array):RubyString{
        var base: int =  args.length == 0 ? 10 : num2int(args[0]);
        if (base < 2 || base > 36) {
            throw getRuntime().newArgumentError("illegal radix " + base);
            }
        return getRuntime().newString(Convert.longToByteList(value, base));
        }

    // fix_id2name
    public function id2name():IRubyObject{
        var symbol: RubySymbol =  RubySymbol.getSymbolLong(getRuntime(), value);
        
        if (symbol != null) return getRuntime().newString(symbol.asJavaString());

        return getRuntime().getNil();
    }

    // fix_to_sym
    public  function to_sym():IRubyObject{
        var symbol: RubySymbol =  RubySymbol.getSymbolLong(getRuntime(), value);
        
        return symbol != null ? symbol : getRuntime().getNil(); 
    }

    // fix_uminus
    public  function op_uminus():IRubyObject{
        if (value == MIN) { // a gotcha
            return RubyBignum.newBignum(getRuntime(), BigInteger.valueOf(value).negate());
        } 
        return RubyFixnum.newFixnum(getRuntime(), -value);
    }
*/
    /** fix_plus
     * 
     */
    public  function op_plus(context:ThreadContext, other:IRubyObject):IRubyObject{
        if (other instanceof RubyFixnum) {
            var otherValue: Number =  RubyFixnum( other).value;
            var result: Number =  value + otherValue;
/*             if ((~(value ^ otherValue) & (value ^ result) & SIGN_BIT) != 0) {
                return RubyBignum.newBignum(getRuntime(), value).op_plus(context, other);
            } */
		return newFixnum(result);
    }
    return null;
/*         if (other instanceof RubyBignum) {
            return ( other).op_plus(context, this);
    }
        if (other instanceof RubyFloat) {
            return getRuntime().newFloat( value + ( other).getDoubleValue());
        }
        return coerceBin(context, "+", other); */
    }

    /** fix_minus
     * 
     */
    public  function op_minus(context:ThreadContext, other:IRubyObject):IRubyObject{
        if (other instanceof RubyFixnum) {
            var otherValue: Number =  RubyFixnum( other).value;
            var result: Number =  value - otherValue;           
            return newFixnum(result);
        }
        return null; 
        /* else if (other instanceof RubyBignum) {
            return RubyBignum.newBignum(getRuntime(), value).op_minus(context, other);
        } else if (other instanceof RubyFloat) {
            return getRuntime().newFloat(value - ( other).getDoubleValue());
        }
        return coerceBin(context, "-", other); */
    }

    /** fix_mul
     * 
     */
    public  function op_mul(context:ThreadContext, other:IRubyObject):IRubyObject{
        if (other instanceof RubyFixnum) {
            var otherValue: Number =  RubyFixnum( other).value;
            if (value == 0) {
                return RubyFixnum.zero(getRuntime());
            }
            var result: Number =  value * otherValue;
            var r: IRubyObject =  newFixnum(getRuntime(),result);
           /*  if(RubyNumeric.fix2long(r) != result || result/value != otherValue) {
                return  RubyBignum.newBignum(getRuntime(), value).op_mul(context, other);
            } */
            return r;
        }
        return null; 
        /* else if (other instanceof RubyBignum) {
            return ( other).op_mul(context, this);
        } else if (other instanceof RubyFloat) {
            return getRuntime().newFloat(value * ( other).getDoubleValue());
        }
        return coerceBin(context, "*", other); */
    }
    
    /** fix_div
     * here is terrible MRI gotcha:
     * 1.div 3.0 -> 0
     * 1 / 3.0   -> 0.3333333333333333
     * 
     * MRI is also able to do it in one place by looking at current frame in rb_num_coerce_bin:
     * rb_funcall(x, ruby_frame->orig_func, 1, y);
     * 
     * also note that RubyFloat doesn't override Numeric.div
     */
    public  function div_div(context:ThreadContext, other:IRubyObject):IRubyObject{
        return idiv(context, other, "div");
    }
    	
    public  function op_div(context:ThreadContext, other:IRubyObject):IRubyObject{
        return idiv(context, other, "/");
    }

    public  function idiv(context:ThreadContext, other:IRubyObject, method:String):IRubyObject{
        if (other instanceof RubyFixnum) {
            var x: Number =  value;
            var y: Number =  RubyFixnum( other).value;
            
            if (y == 0) {
                throw getRuntime().newZeroDivisionError();
            }
            
            var div: Number =  x / y;
            var mod: Number =  x % y;

            if (mod < 0 && y > 0 || mod > 0 && y < 0) {
                div -= 1;
            }

            return getRuntime().newFixnum(div);
        } 
        return coerceBin(context, method, other);
    }
        
    /** fix_mod
     * 
     */
    public  function op_mod(context:ThreadContext, other:IRubyObject):IRubyObject{
        if (other instanceof RubyFixnum) {
            // Java / and % are not the same as ruby
            var x: Number =  value;
            var y: Number =  RubyFixnum( other).value;

            if (y == 0) {
            	throw getRuntime().newZeroDivisionError();
    }

            var mod: Number =  x % y;

            if (mod < 0 && y > 0 || mod > 0 && y < 0) {
                mod += y;
            }
                
            return getRuntime().newFixnum(mod);
	            }
        return coerceBin(context, "%", other);
    }
                
    /** fix_divmod
     * 
     */
    public override function divmod(context:ThreadContext, other:IRubyObject):IRubyObject{
        if (other instanceof RubyFixnum) {
            var x: Number =  value;
            var y: Number =  RubyFixnum( other).value;
            var runtime: Ruby =  getRuntime();

            if (y == 0) {
                throw runtime.newZeroDivisionError();
                }

            var div: Number =  x / y;
            var mod: Number =  x % y;

            if (mod < 0 && y > 0 || mod > 0 && y < 0) {
                div -= 1;
                mod += y;
            }

            var fixDiv: IRubyObject =  RubyFixnum.newFixnum(getRuntime(), div);
            var fixMod: IRubyObject =  RubyFixnum.newFixnum(getRuntime(), mod);

            return RubyArray.newArray(runtime, fixDiv, fixMod);

    	}
        return coerceBin(context, "divmod", other);
    }
    	
    /** fix_quo
     * 
     */
    public override function quo(context:ThreadContext, other:IRubyObject):IRubyObject{
        if (other instanceof RubyFixnum) {
            return RubyFloat.newFloat(getRuntime(), value
                    / RubyFixnum( other).value);
    }
        return coerceBin(context, "quo", other);
	            }

    /** fix_pow 
     * 
     */
    public  function op_pow(context:ThreadContext, other:IRubyObject):IRubyObject{
        if(other instanceof RubyFixnum) {
            var b: Number =  RubyFixnum( other).value;
            if (b == 0) {
                return RubyFixnum.one(getRuntime());
            }
            if (b == 1) {
                return this;
            }
            if (b > 0) {
                return RubyBignum.newBignum(getRuntime(), value).op_pow(context, other);
            }
            return RubyFloat.newFloat(getRuntime(), Math.pow(value, b));
        } else if (other instanceof RubyFloat) {
            return RubyFloat.newFloat(getRuntime(), Math.pow(value, ( other)
                    .getDoubleValue()));
        }
        return coerceBin(context, "**", other);
    }
            
    /** fix_abs
     * 
     */
    public override function abs():IRubyObject{
        if (value < 0) {
            // A gotcha for Number.MIN_VALUE: value = -value
/*             if (value == Number.MIN_VALUE) {
                return RubyBignum.newBignum(
                        getRuntime(), BigInteger.valueOf(value).negate());
            } */
            return RubyFixnum.newFixnum(getRuntime(), -value);
        }
        return this;
    }
            
    /** fix_equal
     * 
     */
    public override function op_equal(context:ThreadContext, other:IRubyObject):IRubyObject{
        if (other instanceof RubyFixnum) {
            return RubyBoolean.newBoolean(getRuntime(), value == RubyFixnum( other).value);
        }
        return super.op_num_equal(context, other);
    }

    /** fix_cmp
     * 
     */
    public override function op_cmp(context:ThreadContext, other:IRubyObject):IRubyObject{
        if (other instanceof RubyFixnum) {
            var otherValue: Number =  RubyFixnum( other).value;
            if (value == otherValue) {
                return RubyFixnum.zero(getRuntime());
    }
            if (value > otherValue) {
                return RubyFixnum.one(getRuntime());
            }
            return RubyFixnum.minus_one(getRuntime());
        }
        return coerceCmp(context, "<=>", other);
    }

    /** fix_gt
     * 
     */
    public  function op_gt(context:ThreadContext, other:IRubyObject):IRubyObject{
        if (other instanceof RubyFixnum) {
            return RubyBoolean.newBoolean(getRuntime(), value > RubyFixnum( other).value);
    }
        return coerceRelOp(context, ">", other);
    }

    /** fix_ge
     * 
     */
    public  function op_ge(context:ThreadContext, other:IRubyObject):IRubyObject{
        if (other instanceof RubyFixnum) {
            return RubyBoolean.newBoolean(getRuntime(), value >= RubyFixnum( other).value);
            }
        return coerceRelOp(context, ">=", other);
    }

    /** fix_lt
     * 
     */
    public  function op_lt(context:ThreadContext, other:IRubyObject):IRubyObject{
        if (other instanceof RubyFixnum) {
            return RubyBoolean.newBoolean(getRuntime(), value < RubyFixnum( other).value);
        }
        return coerceRelOp(context, "<", other);
    }
        
    /** fix_le
     * 
     */
    public  function op_le(context:ThreadContext, other:IRubyObject):IRubyObject{
        if (other instanceof RubyFixnum) {
            return RubyBoolean.newBoolean(getRuntime(), value <= RubyFixnum( other).value);
    }
        return coerceRelOp(context, "<=", other);
    }
/*
    //fix_rev

    public  function op_neg():IRubyObject{
        return newFixnum(~value);
    	}
    	
    // fix_and

    public  function op_and(context:ThreadContext, other:IRubyObject):IRubyObject{
        if (other instanceof RubyFixnum) {
            return newFixnum(value & ( other).value);
        }
        if (other instanceof RubyBignum) {
            return ( other).op_and(context, this);
        }

        return newFixnum(value & num2long(other));
    }

    // fix_or 

    public  function op_or(context:ThreadContext, other:IRubyObject):IRubyObject{
        if (other instanceof RubyFixnum) {
            return newFixnum(value | ( other).value);
        }
        if (other instanceof RubyBignum) {
            return ( other).op_or(context, this);
        }

        return newFixnum(value | num2long(other));
    }

    //fix_xor 

    public  function op_xor(context:ThreadContext, other:IRubyObject):IRubyObject{
        if(other instanceof RubyFixnum) {
            return newFixnum(value ^ RubyFixnum( other).value);
        }
        if (other instanceof RubyBignum) {
            return ( other).op_xor(context, this);
        }

        return newFixnum(value ^ num2long(other));
    }

    /** fix_aref 
     * 
     *
    public  function op_aref(other:IRubyObject):IRubyObject{
        if(other instanceof RubyBignum) {
            var big: RubyBignum =   other;
            var tryFix: RubyObject =  RubyBignum.bignorm(getRuntime(), big.getValue());
            if (!(tryFix instanceof RubyFixnum)) {
                if (big.getValue().signum() == 0 || value >= 0) {
                    return RubyFixnum.zero(getRuntime());
        	}
                return RubyFixnum.one(getRuntime());
        }
    }

        var otherValue: Number =  num2long(other);
            
        if (otherValue < 0) {
            return RubyFixnum.zero(getRuntime());
		    } 
		      
        if (BIT_SIZE - 1 < otherValue) {
            if (value < 0) {
                return RubyFixnum.one(getRuntime());
        }
            return RubyFixnum.zero(getRuntime());
        }
        
        return (value & (1 << otherValue)) == 0 ? RubyFixnum.zero(getRuntime()) : RubyFixnum.one(getRuntime());
    }

    // fix_lshift 
    public  function op_lshift(other:IRubyObject):IRubyObject{
        var width: Number =  num2long(other);

            if (width < 0) {
            return op_rshift(RubyFixnum.newFixnum(getRuntime(), -width));
		    }
    	
        if (width == 0) {
            return this;
    }

        if (width > BIT_SIZE - 1 || ((~0 << BIT_SIZE - width - 1) & value) != 0) {
            return RubyBignum.newBignum(getRuntime(), value).op_lshift(other);
        }
        
        return newFixnum(value << width);
    }

    // fix_rshift 
    public  function op_rshift(other:IRubyObject):IRubyObject{
        var width: Number =  num2long(other);

        if (width < 0) {
            return op_lshift(RubyFixnum.newFixnum(getRuntime(), -width));
    }
    
        if (width == 0) {
            return this;
    }

        if (width >= BIT_SIZE - 1) {
            if (value < 0) {
                return RubyFixnum.minus_one(getRuntime());
    }
            return RubyFixnum.zero(getRuntime());
        }

        return newFixnum(value >> width);
        }
*/
    /** fix_to_f 
     * 
     */
    public  function to_f():IRubyObject{
        return RubyFloat.newFloat(getRuntime(), value);
    }

    /** fix_size 
     * 
     */
    public  function size():IRubyObject{
        return newFixnum(((BIT_SIZE + 7) / 8));
        }

    /** fix_zero_p 
     * 
     */
    public override function zero_p():IRubyObject{
        return RubyBoolean.newBoolean(getRuntime(), value == 0);
    }

    public override function id():IRubyObject{
        if (value <= Number.MAX_VALUE / 2 && value >= Number.MIN_VALUE / 2)
            return newFixnum(2 * value + 1);
        return super.id();
    }

/*     public override function taint():IRubyObject{
        return this;
    }

    public override function freeze():IRubyObject{
        return this;
    } 
    
    // Piece of mri rb_to_id
    public override function asJavaString():String{
        getRuntime().getWarnings().warn(ID.FIXNUMS_NOT_SYMBOLS, "do not use Fixnums as Symbols");
        
        // FIXME: I think this chunk is equivalent to MRI id2name (and not our public method 
        // id2name).  Make into method if used more than once.  
        var symbol: RubySymbol =  RubySymbol.getSymbolLong(getRuntime(), value);
        
        if (symbol == null) {
            throw getRuntime().newArgumentError("" + value + " is not a symbol");
        }
        
        return symbol.asJavaString();
    }

    public static function unmarshalFrom(input:UnmarshalStream):RubyFixnum{
        return input.getRuntime().newFixnum(input.unmarshalInt());
    } */

    /*  ================
     *  Singleton Methods
     *  ================ 
     */

    /** rb_fix_induced_from
     * 
     */
    public static function induced_from(recv:IRubyObject, other:IRubyObject):IRubyObject{
        return RubyNumeric.num2fix(other);
    }    

}
}